﻿using System;
using System.Xml;
using System.Xml.XPath;
using XmlStudio.DataLayer;
using XmlStudio.Interfaces;
using XmlStudio.Models.Events;
using XmlStudio.Utilities;

namespace XmlStudio.Models.Actions {
    /// <summary>
    /// Action responsible for renamig elements.
    /// </summary>
    internal sealed class RenameElementAction : XmlModelAction {
        #region Fields
        #region Private
        /// <summary>
        /// Node to rename.
        /// </summary>
        private BigXmlNodeInternal nodeToRename;

        ///// <summary>
        ///// Old <see cref="XmlElement"/>.
        ///// </summary>
        //private XmlNode oldElement = null;

        /// <summary>
        /// New name to set.
        /// </summary>
        private string newLocalName = string.Empty;

        /// <summary>
        /// Namespace uri of the element.
        /// </summary>
        private string newNamespaceURI;

        /// <summary>
        /// Prefix of the element.
        /// </summary>
        private string newPrefix;

        ///// <summary>
        ///// Indicates whether the element has a namespace or not.
        ///// </summary>
        //private bool withNamespace = false;

        private string oldPrefix = string.Empty;
        private string oldLocalName = string.Empty;
        private string oldNamespaceURI = string.Empty;
        #endregion
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="RenameElementAction"/> class.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <param name="node">Reference to the <see cref="BigXmlNode"/> instance.</param>
        /// <param name="name">New name of the element.</param>
        public RenameElementAction(IXmlModelWithNotifications model, BigXmlNodeInternal node, string name)
            : base(model) {
            this.nodeToRename = node;
            this.wasModified = node.Modified;
            this.newLocalName = name;
            //this.withNamespace = false;
            this.address = node.Address.DeepClone<int[]>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RenameElementAction"/>.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <param name="node">The node.</param>
        /// <param name="localName">Name of the local.</param>
        /// <param name="namespaceURI">The namespace URI.</param>
        /// <param name="prefix">The prefix.</param>
        public RenameElementAction(IXmlModelWithNotifications model, BigXmlNodeInternal node, string localName, string namespaceURI, string prefix)
            : base(model) {
            this.nodeToRename = node;
            this.wasModified = node.Modified;
            this.newLocalName = localName;
            this.newNamespaceURI = namespaceURI;
            this.newPrefix = prefix;
            //this.withNamespace = true;
            this.address = node.Address.DeepClone<int[]>();
        }
        #endregion

        #region Methods
        #region Protected
        /// <summary>
        /// Executes the action.
        /// </summary>
        protected override void ExecuteCore() {
            if(this.nodeToRename.XmlNode is XmlElement) {
                var navigator = this.nodeToRename.XmlNode.CreateNavigator();
                this.oldPrefix = navigator.Prefix;
                this.oldLocalName = navigator.Name;
                this.oldNamespaceURI = navigator.NamespaceURI;

                var newElement = RenameNode(this.nodeToRename.XmlNode as XmlElement, this.newPrefix, this.newLocalName, this.newNamespaceURI);

                // remove old node from dictionary
                this.model.RemoveNode(nodeToRename.XmlNode);

                this.nodeToRename.XmlNode = newElement;
                this.nodeToRename.SetModified(true);

                // add the new node
                this.model.AddNode(this.nodeToRename.XmlNode, this.nodeToRename);

                this.model.SetModified(true);
                this.model.OnNodeUpdated(new NodeUpdatedEventArgs<BigXmlNode>(this.nodeToRename));
                this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
            } else {
                throw new InvalidOperationException("Operation is permitted only on elements!");
            }
        }

        /// <summary>
        /// Executes the undo action.
        /// </summary>
        protected override void UnExecuteCore() {
            if(this.nodeToRename.XmlNode is XmlElement) {
                var oldElement = RenameNode(this.nodeToRename.XmlNode as XmlElement, this.oldPrefix, this.oldLocalName, this.oldNamespaceURI);

                // remove old node from dictionary
                this.model.RemoveNode(nodeToRename.XmlNode);

                this.nodeToRename.XmlNode = oldElement;
                this.nodeToRename.SetModified(wasModified);

                // add the new node
                this.model.AddNode(this.nodeToRename.XmlNode, this.nodeToRename);

                this.model.SetModified(this.modelWasModified);
                this.model.OnNodeUpdated(new NodeUpdatedEventArgs<BigXmlNode>(this.nodeToRename));
                this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
            } else {
                throw new InvalidOperationException("Operation is permitted only on elements!");
            }
        }
        #endregion

        #region Private
        static XmlNode RenameNode(XmlNode e, string prefix, string localName, string namespaceURI) {
            var doc = e.OwnerDocument;
            var newNode = doc.CreateNode(e.NodeType, prefix, localName, namespaceURI);
            while(e.HasChildNodes) {
                newNode.AppendChild(e.FirstChild);
            }

            var ac = e.Attributes;
            while(ac.Count > 0) {
                newNode.Attributes.Append(ac[0]);
            }

            var parent = e.ParentNode;
            if(parent != null) {
                parent.ReplaceChild(newNode, e);
            }

            return newNode;
        }
        #endregion
        #endregion
    }
}